package lab_2_server.handlers;


import ProtocolXML.* ;
import ProtocolXML.NotCompatibilityTypeException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Set;
import lab_2_server.*;


/**
 * Handler for clients
 * @author leshukov
 */
public class ServerHandler extends AbstractHandler implements Runnable {

    public ServerHandler(Server newServer, ServerID newServerID) {
        setServer(newServer);
        setServerID(newServerID);
    }

    
    /** Call whis constructor, if connect with other server also is it */
    public ServerHandler( Server newServer, Socket newSocket,
            InputStream inStream, OutputStream outStream )
    {        
        setServer(newServer);
        setSocket(newSocket);
        setIStream(inStream);
        setOStream(outStream);

        

        ServerID servID = new ServerID( getSocket().getInetAddress().getHostName(), getSocket().getPort(), "Ext_serv" ) ;
        setServerID( servID ) ;
        getServerID().setConnected( true ) ;

        getServer().getServersID().add( servID ) ;

        //! FIXME определить ИП
    }


    @Override
    public void run() {
        if( getSocket() == null ) {
            Socket socket = getServerID().connect() ;
            if( socket == null ) {
                getServerID().setConnected( true ) ;
                //Not connect with server, to exit
                return ;
            }
            getServerID().setConnected( true ) ;
            setSocket( socket );
            initStreams() ;

            //Send to server "autorization" message (We connect to other server)
            Message msg = new Message() ;
            msg.msgIsServerID( new ServerID( "0.0.0.0", 0, "null") ) ;
        }

        //Add ServerHAndler to GUI
        getServer().getMonitor().addServer( this );

        //Add to ServerHandler's list
        getServer().getServerHandlers().add( this ) ;

        //Send to server IDs of other servers
        Message msgWithServ = new Message();
        for ( ServerID servID : getServer().getServersID() ) {
            msgWithServ.msgIsServerID( servID );
            writeMessage( msgWithServ );
        }

        //Send to server connected users
        Message msgWithUser = new Message();
        for ( ClientHandler clientHandler : getServer().getClientsHandlers() ) {
            msgWithUser.msgIsUserConnected( clientHandler.getUserID(), clientHandler.getNickname(0) );
            writeMessage( msgWithUser );
        }

        //Main cycle
        while( !Thread.currentThread().isInterrupted() ) {
            Message msg = null ;
            try {
                msg = Message.read( getIStream( ));
            } catch ( Exception ex ) {
                MyLogger.putMessange( "Not can read message from stream. (ServerHandler::run) " + ex ) ;
                disconnect() ;
                return ;
            }
            processMessage( msg ) ;
        }
    }


    /**Disconnect server*/
    public void disconnect() {
        if( getSocket() == null ) {
            getServerID().setConnected( false ) ;

            //Remove this Handler from Server's Handlers map
            Set<Integer> usersID = getIdToNickMap().keySet() ;
            for( Integer id : usersID ) {
                getServer().getMapIdToHandler().remove( id ) ;
            }
            getServer().getMonitor().delServer( this ) ;
        }
        else {
            Message discMessage = new Message() ;
            for( ClientHandler currHandler : getServer().getClientsHandlers() ) {
                discMessage.msgIsUserDisconnect( currHandler.getUserID() ) ;
                writeMessage( discMessage ) ;
            }
        }
    }


    /** Init Streams */
    private void initStreams() {
        try {
            if (getIStream() == null) {
                setIStream(getSocket().getInputStream());
            }
            if (getOStream() == null) {
                setOStream(getSocket().getOutputStream());
            }
        } catch (IOException ex) {
            MyLogger.putMessange( "Not can get I/O stream. (ServerHandler::initStreams)" ) ;
            Thread.currentThread().interrupt() ;
        }
    }


    /**
     * Method handl message
     * @param msg - message for process
     */
    private void processMessage(Message msg) {
        MyLogger.putMessange( "Getted message" ) ;
        TypeOfMsg typeOfMsg = msg.getTypeOfMsg() ;
        try {
            switch ( typeOfMsg ) {
                case DISCONNECT : {
                    MyLogger.putMessange( "DISCONNECT"  ) ;
                    int userID = msg.getDisconnectUserId() ;

                    int localID = getLocalIDtoOtherID().get( userID ) ;

                    getServer().getMapIdToHandler().remove( localID ) ;

                    Message notifyMessage = new Message() ;
                    notifyMessage.msgIsUserDisconnect( localID ) ;
                    //Notify to users
                    for( ClientHandler clientHAndler : getServer().getClientsHandlers() ) {
                        clientHAndler.writeMessage( notifyMessage ) ;
                    }

                    getIdToNickMap().remove( localID ) ;
                    getLocalIDtoOtherID().remove( localID ) ;
                    break ;
                }
                case SEND_MESSAGE_SRV : {
                    MyLogger.putMessange( "SEND_MESSAGE_SRV" ) ;
                    int receiverID = msg.getIdReceiver() ;
                    String text = msg.getMsg() ;

                    int realSenderID = msg.getSenderId() ;
                    int localSenderID = getLocalIDtoOtherID().get( realSenderID ) ;

                    msg.msgIsSendingToSrv( receiverID, localSenderID, text) ;

                    AbstractHandler handler = getServer().getMapIdToHandler().get( receiverID ) ;
                    handler.writeMessage( msg ) ;
                    break ;
                }
                case SERVER_INF : {
                    MyLogger.putMessange( "SERVER_INF" ) ;
                    ServerID servID = msg.getServerID() ;
                    getServer().getServersID().add( servID ) ;
                    //!FIXME Инициировать соединение с другими серверами (не обязательно)
                    break ;
                }
                case USER_CONNECT : {
                    MyLogger.putMessange( "USER_CONNECT" ) ;
                    int userID = msg.getConnectedUserID() ;
                    int newLocalUserID = getServer().getNewId() ;
                    String nickname = msg.getConnectedUserNick() ;

                    getLocalIDtoOtherID().put( newLocalUserID, userID ) ;

                    getIdToNickMap().put( newLocalUserID, nickname ) ;
                    getServer().getMapIdToHandler().put( newLocalUserID, this ) ;


                    Message notifyMessage = new Message() ;
                    notifyMessage.msgIsUserConnected( newLocalUserID, nickname ) ;
                    
                    //Notify users
                    for( ClientHandler currentHandler : getServer().getClientsHandlers() ) {
                        currentHandler.writeMessage( notifyMessage ) ;
                    }
                    break ;
                }
                default: {
                    MyLogger.putMessange( "Getted unexpected msg " +
                            "(ServerHandler::processMessage). " + msg.getTypeOfMsg() ) ;
                    //AUTHORIZATION, CREATE_GROUP, INV_USER_TO_GROUP, SEND_MESSAGE_USR
                }
            }
        }
        catch ( NotCompatibilityTypeException ex) {
            MyLogger.putMessange( "Type of msg not compatibility with " +
                    "method (ServerHandler::processMessage). " + msg.getTypeOfMsg() );
        }
    }

    @Override
    public void writeMessage(Message msg) {
        if( msg.getTypeOfMsg() == TypeOfMsg.SEND_MESSAGE_USR ) {
            try {
                int localReceiverID = msg.getIdReceiver() ;
                int senderID = msg.getSenderId() ;
                String text = msg.getMsg() ;

                int realReceiverID = getLocalIDtoOtherID().get( localReceiverID ) ;

                msg.msgIsSendingToSrv( realReceiverID, senderID, text );
            }
            catch ( NotCompatibilityTypeException ex) {
                MyLogger.putMessange( "Exception in ServerHandler:writeMessage. " + ex ) ;
            }
        }
        super.writeMessage(msg);
    }



   
    /**
     * Method use for returning NickName
     * @param idOfUser - id of user,
     * @return
     */
    @Override
    public String getNickname(int idOfUser) {
        return getIdToNickMap().get( idOfUser ) ;
    }


    /**
     * @return String in view "(serverID)"
     */
    @Override
    public String toString() {
//        StringBuilder builder = new StringBuilder() ;
//        builder.append( getServerID().getAlias() ) ;
//        builder.append( "(" ) ;
//        builder.append( getServerID() ) ;
//        builder.append( ")" ) ;
//
//        return builder.toString() ;
        return getServerID().toString() ;
    }


    //-----Fields-----
    
    private ServerID serverID ;

    private HashMap<Integer, String> idToNickMap = new HashMap<Integer, String>() ;

    private HashMap<Integer, Integer> localIDtoOtjerID = new HashMap<Integer, Integer>() ;


    //-----GETTERS and SETTERS-----

    private ServerID getServerID() {
        return serverID;
    }
    private void setServerID(ServerID serverID) {
        this.serverID = serverID;
    }
    private HashMap<Integer, String> getIdToNickMap() {
        return idToNickMap;
    }
    private HashMap<Integer, Integer> getLocalIDtoOtherID() {
        return localIDtoOtjerID;
    }
}
